﻿using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace EBA.Linq
{
    public static class DynamicLinq
    {
        public static IQueryable<T> Where<T>(this IQueryable<T> source, string where)
        {
            if (string.IsNullOrWhiteSpace(where))
            {
                return source;
            }
            else
            {
                var func = where.ToWhere<T>();
                if (func == null)
                {
                    return source;
                }
                else
                {
                    return source.Where(func);
                }
            }
        }

        public static IQueryable<T> Fetch<T>(this IQueryable<T> source, int pageIndex, int pageSize)
        {
            var skip = (pageIndex - 1) * pageSize;

            return source.Skip(skip > 0 ? skip : 0).Take(pageSize);
        }

        //private static IOrderedQueryable<T> OrderBy<T>(IQueryable<T> source, ParameterExpression parameter, System.Reflection.PropertyInfo pi, bool isDesc)
        //{

        //    if (pi.PropertyType == typeof(int))
        //    {

        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, int>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, int>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //    }
        //    else if (pi.PropertyType == typeof(string))
        //    {
        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, string>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, string>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //    }
        //    else if (pi.PropertyType == typeof(decimal))
        //    {
        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, decimal>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, decimal>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //    }
        //    else if (pi.PropertyType == typeof(double))
        //    {
        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, double>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, double>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //    }
        //    else if (pi.PropertyType == typeof(DateTime))
        //    {
        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, DateTime>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, DateTime>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //    }
        //    else if (pi.PropertyType == typeof(float))
        //    {
        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, float>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, float>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //    }
        //    else if (pi.PropertyType.IsEnum)
        //    {
        //        if (isDesc)
        //        {
        //            return source.OrderByDescending(LambdaExpression.Lambda<Func<T, Enum>>(LambdaExpression.Name(parameter, pi), parameter));
        //        }
        //        else
        //        {
        //            return source.OrderBy(LambdaExpression.Lambda<Func<T, int>>(LambdaExpression.Name(parameter, pi), parameter));//
        //        }
        //    }
        //    else
        //    {
        //        throw new NotSupportedException(pi.PropertyType.ToString());
        //    }


        //}

        private static IOrderedQueryable<T> ThenBy<T>(IOrderedQueryable<T> source, ParameterExpression parameter, System.Reflection.PropertyInfo pi, bool isDesc)
        {

            if (pi.PropertyType == typeof(int))
            {

                if (isDesc)
                {
                    return source.ThenByDescending(LambdaExpression.Lambda<Func<T, int>>(LambdaExpression.Property(parameter, pi), parameter));
                }
                else
                {
                    return source.ThenBy(LambdaExpression.Lambda<Func<T, int>>(LambdaExpression.Property(parameter, pi), parameter));
                }
            }
            else if (pi.PropertyType == typeof(string))
            {
                if (isDesc)
                {
                    return source.ThenByDescending(LambdaExpression.Lambda<Func<T, string>>(LambdaExpression.Property(parameter, pi), parameter));
                }
                else
                {
                    return source.ThenBy(LambdaExpression.Lambda<Func<T, string>>(LambdaExpression.Property(parameter, pi), parameter));
                }
            }
            else if (pi.PropertyType == typeof(decimal))
            {
                if (isDesc)
                {
                    return source.ThenByDescending(LambdaExpression.Lambda<Func<T, decimal>>(LambdaExpression.Property(parameter, pi), parameter));
                }
                else
                {
                    return source.ThenBy(LambdaExpression.Lambda<Func<T, decimal>>(LambdaExpression.Property(parameter, pi), parameter));
                }
            }
            else if (pi.PropertyType == typeof(double))
            {
                if (isDesc)
                {
                    return source.ThenByDescending(LambdaExpression.Lambda<Func<T, double>>(LambdaExpression.Property(parameter, pi), parameter));
                }
                else
                {
                    return source.ThenBy(LambdaExpression.Lambda<Func<T, double>>(LambdaExpression.Property(parameter, pi), parameter));
                }
            }
            else if (pi.PropertyType == typeof(DateTime))
            {
                if (isDesc)
                {
                    return source.ThenByDescending(LambdaExpression.Lambda<Func<T, DateTime>>(LambdaExpression.Property(parameter, pi), parameter));
                }
                else
                {
                    return source.ThenBy(LambdaExpression.Lambda<Func<T, DateTime>>(LambdaExpression.Property(parameter, pi), parameter));
                }
            }
            else if (pi.PropertyType == typeof(float))
            {
                if (isDesc)
                {
                    return source.ThenByDescending(LambdaExpression.Lambda<Func<T, float>>(LambdaExpression.Property(parameter, pi), parameter));
                }
                else
                {
                    return source.ThenBy(LambdaExpression.Lambda<Func<T, float>>(LambdaExpression.Property(parameter, pi), parameter));
                }
            }
            else
            {
                throw new NotSupportedException(pi.PropertyType.ToString());
            }


        }


        public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string orderby) //where T : class
        {
            if (string.IsNullOrWhiteSpace(orderby))
            {
                return source;
            }
            else
            {
                var orderbys = orderby.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                return source.OrderBy(orderbys);
            }
        }

        public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string[] orderbys)
        {
            var items = orderbys == null ? new string[0] : orderbys.Where(i => string.IsNullOrWhiteSpace(i.Trim()) == false).ToArray();
            if (items == null || items.Length == 0)
            {
                return source;
            }
            else
            {
                var entityType = typeof(T);
                var parameter = LambdaExpression.Parameter(entityType, "i");

                IOrderedQueryable<T> query = null;

                for (var i = 0; i < items.Length; i++)
                {
                    var item = items[i];
                    if (item.EndsWith("desc"))
                    {
                        #region desc
                        var name = item.Substring(2, item.Length - 6).Trim();

                        var pi = entityType.GetProperty(name);
                        if (pi == null)
                        {
                            pi = GetParentProperty(entityType, name, ref parameter);
                        }

                        Expression propertyAccessExpression = Expression.MakeMemberAccess(parameter, pi);
                        LambdaExpression le = Expression.Lambda(propertyAccessExpression, parameter);

                        if (pi == null)
                        {
                            throw new MissingFieldException(item);
                        }

                        if (i == 0)
                        {
                            MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { entityType, pi.PropertyType }, source.Expression, Expression.Quote(le));
                            return source.Provider.CreateQuery<T>(resultExp);
                        }
                        else
                        {
                            query = ThenBy<T>(query, parameter, pi, true);
                        }
                        #endregion
                    }
                    else
                    {
                        #region asc
                        var name = item.Substring(2, item.Length - 2).Trim();

                        var pi = entityType.GetProperty(name);

                        if (pi == null)
                        {
                            pi = GetParentProperty(entityType, name, ref parameter);
                        }

                        Expression propertyAccessExpression = Expression.MakeMemberAccess(parameter, pi);
                        LambdaExpression le = Expression.Lambda(propertyAccessExpression, parameter);
                        if (pi == null)
                        {
                            throw new MissingFieldException(item);
                        }

                        if (i == 0)
                        {
                            MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { entityType, pi.PropertyType }, source.Expression, Expression.Quote(le));
                            query = source.Provider.CreateQuery<T>(resultExp) as IOrderedQueryable<T>;
                        }
                        else
                        {
                            query = ThenBy<T>(query, parameter, pi, false);
                        }
                        #endregion
                    }
                }

                return query;
            }

        }

        private static PropertyInfo GetParentProperty(Type type, string name, ref ParameterExpression parameter)
        {
            PropertyInfo info = null;
            if (type.GetProperties().Length == 0)
            {
                var parents = type.GetInterfaces();
                foreach (var parent in parents)
                {
                    info = parent.GetProperty(name);
                    if (info != null)
                    {
                        break;
                    }
                    //else
                    //{
                    //    info = GetReferenceProperty(parent, name, ref parameter, type);
                    //    if (info != null)
                    //    {
                    //        break;
                    //    }
                    //}
                }
            }
            //else
            //{
            //    info = GetReferenceProperty(type, name, ref parameter, type);
            //}
            return info;
        }


        private static PropertyInfo GetReferenceProperty(Type type, string name, ref ParameterExpression parameter, Type oldtype)
        {
            PropertyInfo info = null;
            foreach (var property in type.GetProperties())
            {
                var type1 = property.PropertyType;
                if (type1.GetProperties().Length > 0)
                {
                    info = type1.GetProperty(name);
                    if (info != null)
                    {
                        parameter = LambdaExpression.Parameter(type1, "i." + type1.Name);
                        break;
                    }
                }
            }
            return info;
        }
    }
}
